Echo Server
Deadline: 31/3/2011 at 16:00
General Instructions:
This exercise should be carried in groups of
three. You have until Friday, March 11 to email Sotirios Terzis the
group you are going to work in. Anybody that fails to do so will be allocated
into a group by the teaching team. Note that you are not allowed to work
for this exercise with anyone that you worked with in the first one.
The exercise consists of 7 parts, with each part
having its own weight (see below). The above submission deadline is strict
(i.e. no late submissions will be accepted).
For the submission of your exercise you should
produce a single zip file that includes:
The zip file should be submitted using Spider.
For marking you will have to demonstrate your
solution working in the lab to the lecturer or the postgraduate class
demonstrators. Details about these demonstrations will follow in due time.
Exercise Description:
An echo server is a server that echoes back whatever it receives from a client.
For example, if a client sends the server the string Hello there! the server will respond with
the exact data it received from the client—that is, Hello there!
Part A
Write an echo server using the Java networking API.
This server will wait for a client connection using the accept() method. When a client connection is received, the server will
loop, performing the following steps:
The server will break out of the loop only when
it has determined that the client has closed the connection.
The date server shown in the lectures uses the java.io.BufferedReader class. BufferedReader extends the java.io.Reader class, which is used for reading character streams. However, the
echo server cannot guarantee that it will read characters from clients; it may
receive binary data as well. The class java.io.InputStream deals with
data at the byte level rather than the character level. Thus, this echo server
must use an object that extends java.io.InputStream. The read() method in the java.io.InputStream class returns −1 when the client has closed
its end of the socket connection.
To test your server implementation, develop a
client that connects to the server, reads input typed in by the user, until the
user indicates with a special character it wishes to exit, sends the user input
to the server, and writes the server’s responses into a file.
Part B
The above server is single-threaded, meaning the
server cannot respond to concurrent echo clients until the current client
exits. Modify your implementation so that the echo server services each client
in a separate thread.
Part C
Modify your part B implementation so that each
client is serviced using a thread pool. Experiment with the three different
models of thread-pool architecture in order to determine how the behaviour of
the server changes for each of them.
Part D
Servers can be designed to limit the number of open connections.
For example, a server may wish to have only N socket connections at any point in time. As soon as N connections are made, the server will not accept
another incoming connection until an existing connection is released. Modify
your part B implementation to use Java semaphores so that the number of
concurrent connections is limited.
Part E
Create a thread pool using Java synchronization.
Your thread pool will implement the following API:
ThreadPool()–Create a
default-sized thread pool.
ThreadPool(int size)–Create a thread pool of size size.
void add(Runnable task)–Add a task to be performed by a thread in the pool.
void stopPool()–Stop all threads in the pool.
Your pool will first create a number of idle
threads that await work. Work will be submitted to the pool via the add() method, which adds a task implementing the Runnable interface.
The add() method will place the Runnable task into a
queue. Once a thread in the pool becomes available for work, it will check the
queue for any Runnable tasks. If there are such tasks, the idle thread will remove the
task from the queue and invoke its run() method. If the queue is empty, the idle thread will wait to be
notified when work becomes available. (The add() method will perform a notify() when it places a Runnable task into the
queue to possibly awaken an idle thread awaiting work.) The stopPool() method will stop all threads in the pool by invoking their interrupt() method. This of course
requires that Runnable tasks being executed by the thread pool check their interruption
status.
Test your solution to this problem by modifying
your part B implementation so that it now uses your thread pool.
Part F
Modify your part C implementation so that the serve keeps a count of
the number the client connections it receives and the number of total number of
messages echoed. In this implementation each echoed message start with the
sequence number of the connection and the sequence number of the echoed
message, e.g. clients is the 5th connection and sends the overall 10th
message to be “Hi!”, the server will respond “5.10: Hi!”. Utilise the Java
built-in thread synchronisation facilities to ensure that there are no race
conditions in your code.
Part G
Modify your part F implementation so that instead of the Java built-in
synchronisation facilities, it uses locks and condition variables to produce
exactly the same behaviour.
Marking Scheme:
Each of the above parts is marked as follows: